# -*- encoding: utf-8 -*-
'''
@File    :   mk_demo.py
@Time    :   2021/11/27 12:01:23
@Author  :   HMX
@Version :   1.0
@Contact :   kzdhb8023@163.com
'''
# here put the import lib
# 33 19   ？ 35 27
# [23 17] 20 bursty_input
# [35 17] 30 4_61

import pymannkendall as mk
import numpy as np
import pandas as pd
from scipy.spatial.distance import cdist
import random
from scipy.signal import argrelextrema


def trendline(data):
    order=1
    index=[i for i in range(1, len(data)+1)]
    coeffs = np.polyfit(index, list(data), order)
    slope = coeffs[-2]
    if float(slope) > 0:
        return 'increasing'
    else:
        return 'decreasing'


def find_interval(cp_df):
    columns = cp_df.columns
    interval_vector = []
    for metric in columns:
        data = np.array(cp_df[metric])
        cp = np.where(data != 0)

        "不考虑最后一个点"
        # if len(cp[0]) > 1:
        #     for i in range(0, len(cp[0]), 2):
        #         temp = [cp[0][i], cp[0][i+1]]
        #         interval_vector.append(temp)

        "考虑最后一个点"
        if len(cp[0]) > 1:
            for i in range(len(cp[0]) - 1):
                if (cp[0][i + 1] - cp[0][i] <= 33) and (cp[0][i + 1] - cp[0][i] > 17):
                    temp = [cp[0][i], cp[0][i + 1]]
                    interval_vector.append(temp)
            if len(data) - cp[0][-1] < 20:
                interval_vector.append([cp[0][-1], len(data)])

    return interval_vector


def find_single_interval(metric_cp):
    interval_vector = []
    data = np.array(metric_cp)
    cp = np.where(data != 0)

    "不考虑最后一个点"
    # if len(cp[0]) > 1:
    #     for i in range(0, len(cp[0]), 2):
    #         temp = [cp[0][i], cp[0][i+1]]
    #         interval_vector.append(temp)

    "考虑最后一个点"

    if len(cp[0]) > 1:
        for i in range(len(cp[0]) - 1):
            if (cp[0][i + 1] - cp[0][i] <= 33) and (cp[0][i + 1] - cp[0][i] > 17):
                temp = [cp[0][i], cp[0][i + 1]]
                interval_vector.append(temp)
        if len(data) - cp[0][-1] < 20:
            interval_vector.append([cp[0][-1], len(data)])

    return interval_vector


def calculate_distance(interval_vector):
    A = np.array(interval_vector)
    B = np.array(interval_vector)
    dist = cdist(A, B, metric='euclidean')
    return dist


# DBSCAN算法，参数为数据集，Eps为指定半径参数，MinPts为制定邻域密度阈值
def dbscan(Data, metric_list, Eps, MinPts):
    num = len(metric_list)  # 点的个数
    # print("点的个数："+str(num))
    unvisited = [i for i in range(num)]  # 没有访问到的点的列表
    # print(unvisited)
    visited = []  # 已经访问的点的列表
    C = [-1 for i in range(num)]
    # C为输出结果，默认是一个长度为num的值全为-1的列表
    # 用k来标记不同的簇，k = -1表示噪声点
    k = -1
    # 如果还有没访问的点
    while len(unvisited) > 0:
        # 随机选择一个unvisited对象
        p = random.choice(unvisited)
        unvisited.remove(p)
        visited.append(p)
        # N为p的epsilon邻域中的对象的集合
        N = []
        for i in range(num):
            if Data.loc[metric_list[i], metric_list[p]] <= Eps:  # and (i!=p):
                N.append(i)
        # 如果p的epsilon邻域中的对象数大于指定阈值，说明p是一个核心对象
        if len(N) >= MinPts:
            k = k + 1
            # print(k)
            C[p] = k
            # 对于p的epsilon邻域中的每个对象pi
            for pi in N:
                if pi in unvisited:
                    unvisited.remove(pi)
                    visited.append(pi)
                    # 找到pi的邻域中的核心对象，将这些对象放入N中
                    # M是位于pi的邻域中的点的列表
                    M = []
                    for j in range(num):
                        if Data.loc[metric_list[j], metric_list[pi]] <= Eps:  # and (j!=pi):
                            M.append(j)
                    if len(M) >= MinPts:
                        for t in M:
                            if t not in N:
                                N.append(t)
                # 若pi不属于任何簇，C[pi] == -1说明C中第pi个值没有改动
                if C[pi] == -1:
                    C[pi] = k
        # 如果p的epsilon邻域中的对象数小于指定阈值，说明p是一个噪声点
        else:
            C[p] = -1
    return C


def trend_detection(data):
    result = mk.original_test(data, alpha=0.05)  # alpha默认为0.05
    return result


def top1(lst):
    return max(lst, default='列表为空', key=lambda v: lst.count(v))


if __name__ == '__main__':
    # metric_name = pd.read_csv('D:/时间序列/Benchmark/指标挑选/bursty_input-slide_dtw/2_1_100000_60_selected.csv', index_col=0)
    # metric_name = metric_name[(metric_name['key'] == 1) | (metric_name['key'] == 2)]
    # selected = metric_name['metric'].tolist()
    # selected = ['1_executor_cpuTime_count', '1_executor_shuffleLocalBytesRead_count', '1_executor_shuffleRecordsRead_count', '1_executor_shuffleRemoteBytesRead_count', '1_executor_shuffleTotalBytesRead_count', '1_jvm_heap_committed_value', '1_jvm_heap_usage_value', '1_jvm_heap_used_value', '1_jvm_pools_PS-Eden-Space_max_value', '1_jvm_pools_PS-Eden-Space_usage_value', '1_jvm_pools_PS-Eden-Space_used_value', '1_jvm_pools_PS-Old-Gen_used_value', '1_jvm_pools_PS-Survivor-Space_committed_value', '1_jvm_pools_PS-Survivor-Space_max_value', '1_jvm_pools_PS-Survivor-Space_usage_value', '1_jvm_pools_PS-Survivor-Space_used_value', '1_jvm_total_committed_value', '1_jvm_total_used_value', '2_jvm_heap_usage_value', '2_jvm_heap_used_value', '2_jvm_pools_PS-Eden-Space_usage_value', '2_jvm_pools_PS-Eden-Space_used_value', '2_jvm_pools_PS-Survivor-Space_committed_value', '2_jvm_pools_PS-Survivor-Space_max_value', '2_jvm_pools_PS-Survivor-Space_used_value', '2_jvm_total_used_value', 'driver_BlockManager_memory_memUsed_MB_value', 'driver_BlockManager_memory_onHeapMemUsed_MB_value', 'driver_BlockManager_memory_remainingMem_MB_value', 'driver_BlockManager_memory_remainingOnHeapMem_MB_value']

    selected = ['1_executor_cpuTime_count', '1_jvm_heap_usage_value',
                '1_jvm_heap_used_value', '1_jvm_pools_PS-Eden-Space_max_value', '1_jvm_pools_PS-Eden-Space_usage_value',
                '1_jvm_pools_PS-Eden-Space_used_value', '1_jvm_pools_PS-Old-Gen_used_value',
                '1_jvm_pools_PS-Survivor-Space_committed_value', '1_jvm_pools_PS-Survivor-Space_max_value',
                '1_jvm_pools_PS-Survivor-Space_usage_value', '1_jvm_pools_PS-Survivor-Space_used_value',
                '1_jvm_total_committed_value', '1_jvm_total_used_value', '2_jvm_heap_usage_value',
                '2_jvm_heap_used_value', '2_jvm_pools_PS-Eden-Space_usage_value',
                '2_jvm_pools_PS-Eden-Space_used_value', '2_jvm_pools_PS-Survivor-Space_committed_value',
                '2_jvm_pools_PS-Survivor-Space_max_value', '2_jvm_pools_PS-Survivor-Space_used_value',
                '2_jvm_total_used_value', 'driver_BlockManager_memory_memUsed_MB_value',
                'driver_BlockManager_memory_onHeapMemUsed_MB_value', 'driver_BlockManager_memory_remainingMem_MB_value',
                'driver_BlockManager_memory_remainingOnHeapMem_MB_value']

    change_point_df = pd.read_csv('./../指标挑选/结果统计/考虑最后一个点的情况/顺次判断/stalled_input/'
                                  '6_3_200000_76/6_3_200000_76_dtw_smooth_cp_0.2_filter.csv', index_col=0)
    change_point_df = change_point_df[selected]
    dtw = pd.read_csv('./../指标挑选/结果统计/考虑最后一个点的情况/顺次判断/stalled_input/'
                      '6_3_200000_76/6_3_200000_76_dtw_smooth.csv', index_col=0)

    interval_vector = find_interval(change_point_df)
    dist = calculate_distance(interval_vector)
    dist = pd.DataFrame(dist)

    cluster = dbscan(dist, dist.columns.tolist(), 3, int(len(selected)*0.6))
    # print(cluster)
    # print(set(cluster))
    if -1 in set(cluster):
        K = len(set(cluster))-1
    else:
        K = len(set(cluster))

    con = list(zip(cluster, interval_vector))
    for clu in range(K):
        names = globals()
        names['pre'+str(clu)] = [i[1] for i in con if i[0] == clu]
        # print(names['pre'+str(clu)])

        "取均值"
        start = sum([i[0] for i in names['pre' + str(clu)]]) / len(names['pre' + str(clu)])
        end = sum([i[1] for i in names['pre'+str(clu)]])/len(names['pre'+str(clu)])
        print('第{}段故障均值区间：{}，{}'.format(clu + 1, start, end))

        "取最大区间"
        start = min([i[0] for i in names['pre' + str(clu)]])
        end = max([i[1] for i in names['pre' + str(clu)]])
        print('第{}段故障最大区间：{}，{}'.format(clu+1, start, end))

        "取最小区间"
        start = max([i[0] for i in names['pre' + str(clu)]])
        end = min([i[1] for i in names['pre' + str(clu)]])
        print('第{}段故障最小区间：{}，{}'.format(clu + 1, start, end))

    # 找到变点检测完美的时间序列
    # 只在故障时间段检测出了变点
    per = []
    for metric in selected:
        single_interval = find_single_interval(change_point_df[metric])
        match = 0
        if len(single_interval) == K:
            for s in range(K):
                for clu in range(K):
                    names = globals()
                    if single_interval[s] in names['pre'+str(clu)]:
                        match += 1
        if match == K:
            per.append(metric)
    # print("变点检测完美的指标：")
    # print(per)

    # ran = random.sample(per, 1)
    trend_df = pd.DataFrame()
    for ran in per:
        test_object = dtw[ran]
        single_interval = find_single_interval(change_point_df[ran])
        result = []
        for i in range(K):
            interval = test_object[single_interval[i][0]:single_interval[i][1]]
            extreme = argrelextrema(np.array(interval), np.greater)

            # 取中段 分段检测
            begin = single_interval[i][0]
            end = single_interval[i][0] + (single_interval[i][1] - single_interval[i][0])//2
            trend_detect_object_1 = test_object[begin:end]
            result_1 = trendline(np.array(trend_detect_object_1))

            begin = single_interval[i][0] + (single_interval[i][1] - single_interval[i][0])//2
            end = single_interval[i][1]
            trend_detect_object_2 = test_object[begin:end]
            result_2 = trendline(np.array(trend_detect_object_2))

            result.append([result_1, result_2])
        trend_df[ran] = result

    for i in range(len(trend_df)):
        re = trend_df.iloc[i, :].tolist()
        if top1(re) == ['increasing', 'decreasing']:
            print("第{}个故障类型是 bursty_input".format(i+1))
        if top1(re) == ['decreasing', 'increasing']:
            print("第{}个故障类型是 stalled_input".format(i+1))
        if top1(re) == ['decreasing', 'decreasing']:
            print("第{}个故障类型是 stalled_input 最后一个区间".format(i+1))
        if top1(re) == ['increasing', 'increasing']:
            print("第{}个故障类型是 bursty_input 最后一个区间".format(i+1))








